﻿// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System.Collections.Generic;

using Nova.Parsing;
using Nova.Utilities;

namespace Nova.CodeDOM
{
    /// <summary>
    /// Declares an enumerated type, and includes a name and a body with a list of identifiers
    /// (with optional assigned values).  It can also optionally have modifiers and/or a base type.
    /// </summary>
    /// <remarks>
    /// Non-nested enums can be only public or internal, and default to internal.
    /// Nested enums can be any of the 5 access types, and default to private.
    /// Other valid modifiers include: new
    /// Allowable base types are: byte, sbyte, short, ushort, int (default), uint, long, ulong
    /// Enums contain zero or more identifiers as members, which are assigned constant
    /// values which default to 0 and auto-increment.  Alternatively, constant expressions
    /// may be used to manually assign values.
    /// </remarks>
    public class EnumDecl : BaseListTypeDecl
    {
        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create an <see cref="EnumDecl"/> with the specified name.
        /// </summary>
        public EnumDecl(string name, Modifiers modifiers)
            : base(name, modifiers)
        { }

        /// <summary>
        /// Create an <see cref="EnumDecl"/> with the specified name.
        /// </summary>
        public EnumDecl(string name)
            : base(name, Modifiers.None)
        { }

        /// <summary>
        /// Create an <see cref="EnumDecl"/> with the specified name, modifiers, and base type.
        /// </summary>
        public EnumDecl(string name, Modifiers modifiers, Expression baseType)
            : base(name, modifiers, baseType)
        { }

        /// <summary>
        /// Create an <see cref="EnumDecl"/> with the specified name and base type.
        /// </summary>
        public EnumDecl(string name, Expression baseType)
            : base(name, Modifiers.None, baseType)
        { }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// Always <c>true</c> for an enum.
        /// </summary>
        public override bool IsStatic
        {
            get { return true; }
        }

        /// <summary>
        /// Always <c>true</c> for an enum.
        /// </summary>
        public override bool IsEnum
        {
            get { return true; }
        }

        /// <summary>
        /// Always <c>false</c> for an enum.
        /// </summary>
        public override bool IsGenericType
        {
            get { return false; }
        }

        /// <summary>
        /// Always <c>true</c> for an enum.
        /// </summary>
        public override bool IsValueType
        {
            get { return true; }
        }

        /// <summary>
        /// True if this is a bit-flag type enum, otherwise false.
        /// </summary>
        public bool IsBitFlags
        {
            get { return HasAttribute(TypeUtil.FlagsAttributeName); }
            set
            {
                bool isBitFlags = IsBitFlags;
                if (value)
                {
                    // Add the Flags attribute if it doesn't already exist
                    if (!isBitFlags)
                        AttachAnnotation(new Attribute((TypeRef)TypeRef.FlagsAttributeRef.Clone()));
                }
                else
                {
                    // Remove any existing Flags attribute
                    if (isBitFlags)
                        RemoveAttribute(TypeUtil.FlagsAttributeName);
                }
            }
        }

        /// <summary>
        /// The underlying type of the enum (will never be null - defaults to 'int').
        /// </summary>
        public Expression UnderlyingType
        {
            get
            {
                // Any non-default underlying (storage) type is actually stored as the base type, because
                // the syntax appears that way, but the real base type is always System.Enum.  Also, the
                // underlying type must be a primitive integral type, not a user-defined type.  If no
                // storage type is specified, the default is 'int'.
                Expression type = null;
                if (HasBaseTypes)
                    type = BaseTypes[0];
                return (type ?? TypeRef.IntRef);
            }
            set
            {
                // Clear the base type if 'int', otherwise create it or update any existing one
                if (value == null)
                    _baseTypes = null;
                else
                {
                    TypeRefBase typeRefBase = value.SkipPrefixes() as TypeRefBase;
                    if (typeRefBase != null && typeRefBase.IsSameRef(TypeRef.IntRef))
                        _baseTypes = null;
                    else
                        _baseTypes = new ChildList<Expression>(this) { value };
                }
            }
        }

        /// <summary>
        /// The child MultiEnumMemberDecl object that in turn holds all of the EnumMemberDecls.
        /// </summary>
        public MultiEnumMemberDecl MultiEnumMemberDecl
        {
            get
            {
                // Return the first existing MultiEnumMemberDecl (should be only one)
                MultiEnumMemberDecl valueDecl = _body.FindFirst<MultiEnumMemberDecl>();
                if (valueDecl != null)
                    return valueDecl;

                // If none was found, create one now
                valueDecl = new MultiEnumMemberDecl();
                _body.Add(valueDecl);
                return valueDecl;
            }
        }

        /// <summary>
        /// The EnumMemberDecl grandchildren of the EnumDecl (from the MultiEnumMemberDecl child object).
        /// </summary>
        public ChildList<EnumMemberDecl> MemberDecls
        {
            get { return MultiEnumMemberDecl.MemberDecls; }
        }

        /// <summary>
        /// The keyword associated with the <see cref="Statement"/>.
        /// </summary>
        public override string Keyword
        {
            get { return ParseToken; }
        }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Add an <see cref="EnumMemberDecl"/>.
        /// </summary>
        public void Add(EnumMemberDecl enumMemberDecl)
        {
            MultiEnumMemberDecl.Add(enumMemberDecl);
        }

        /// <summary>
        /// Add an <see cref="EnumMemberDecl"/> with the specified name.
        /// </summary>
        public void Add(string name, Expression initialization)
        {
            MultiEnumMemberDecl.Add(name, initialization);
        }

        /// <summary>
        /// Add an <see cref="EnumMemberDecl"/> with the specified name.
        /// </summary>
        public void Add(string name)
        {
            MultiEnumMemberDecl.Add(name, null);
        }

        /// <summary>
        /// Add multiple <see cref="EnumMemberDecl"/>s.
        /// </summary>
        public void Add(params EnumMemberDecl[] enumMemberDecls)
        {
            MultiEnumMemberDecl.AddRange(enumMemberDecls);
        }

        /// <summary>
        /// Add a collection of <see cref="EnumMemberDecl"/>s.
        /// </summary>
        public void AddRange(IEnumerable<EnumMemberDecl> collection)
        {
            MultiEnumMemberDecl.AddRange(collection);
        }

        /// <summary>
        /// Get the base type.
        /// </summary>
        public override TypeRef GetBaseType()
        {
            // The base type is *always* Enum, NOT the underlying type
            return TypeRef.EnumRef;
        }

        /// <summary>
        /// Get the enum member with the specified name.
        /// </summary>
        public EnumMemberRef GetMember(string name)
        {
            return MultiEnumMemberDecl.GetMember(name);
        }

        #endregion

        #region /* PARSING */

        /// <summary>
        /// The token used to parse the code object.
        /// </summary>
        public new const string ParseToken = "enum";

        internal static void AddParsePoints()
        {
            // Enums are only valid with a Namespace or TypeDecl parent, but we'll allow any IBlock so that we can
            // properly parse them if they accidentally end up at the wrong level (only to flag them as errors).
            // This also allows for them to be embedded in a DocCode object.
            Parser.AddParsePoint(ParseToken, Parse, typeof(IBlock));
        }

        /// <summary>
        /// Parse an <see cref="EnumDecl"/>.
        /// </summary>
        public static EnumDecl Parse(Parser parser, CodeObject parent, ParseFlags flags)
        {
            return new EnumDecl(parser, parent);
        }

        protected EnumDecl(Parser parser, CodeObject parent)
            : base(parser, parent)
        {
            MoveComments(parser.LastToken);        // Get any comments before 'enum'
            parser.NextToken();                    // Move past 'enum'
            ParseNameTypeParameters(parser);       // Parse the name.  Type parameters are also handled, although illegal.
            ParseModifiersAndAnnotations(parser);  // Parse any attributes and/or modifiers
            ParseBaseTypeList(parser);             // Parse the optional base-type list

            // If we don't have a base-type list, move any trailing compiler directives to the Postfix position
            if (_baseTypes == null || _baseTypes.Count == 0)
                MoveAnnotations(AnnotationFlags.IsInfix1, AnnotationFlags.IsPostfix);

            // We have to do a special callback check for the body to handle a single identifier in the Block
            // with no '=' or ',' for EnumMemberDecl to parse on.
            parser.SingleUnusedIdentifierParser = EnumMemberDecl.Parse;
            new Block(out _body, parser, this, true);  // Parse the body
            parser.SingleUnusedIdentifierParser = null;

            // Eat any trailing terminator (they are allowed but not required on non-delegate type declarations)
            if (parser.TokenText == ParseTokenTerminator)
                parser.NextToken();

            // Force to a multi-line body if the MultiEnumMemberDecl child is multi-line
            if (_body != null && _body.Count > 0 && _body[0].IsFirstOnLine)
                _body.IsFirstOnLine = true;
        }

        #endregion

        #region /* FORMATTING */

        /// <summary>
        /// True if the code object only requires a single line for display by default.
        /// </summary>
        public override bool IsSingleLineDefault
        {
            get { return true; }
        }

        /// <summary>
        /// Determine a default of 1 or 2 newlines when adding items to a <see cref="Block"/>.
        /// </summary>
        public override int DefaultNewLines(CodeObject previous)
        {
            // Always default to a blank line before an enum declaration
            return 2;
        }

        #endregion
    }
}
